#ifndef __BASEJUDGE_HPP__
#define __BASEJUDGE_HPP__

#include "../utility/ojData.h"
#include "../judge_events/run_code_params.h"
#include "../judge_events/event_context.hpp"
using namespace run_code_event_space;

class CorrectOutComponent {
public:
    std::vector<std::string> outFiles;
    ProcessStatus init(const string& dataPath);
};

template <class T>
class JudgeType
{
public:
    // 支持的语言类别
    int filter_lang = 0;
    std::vector<std::string> inFiles;
    const static std::vector<seccomp_ruler> disableSysCallSet;

    // userFile表示原文件，userCodeFile表示拷贝到userCachePath后的文件，剩下的文件也都以userCodeFile为基准
    std::string userFile; 
    CodeFileInfo userCodeInfo;
    mutable bool userCompileOk = false;

    std::string spjFile;
    CodeFileInfo spjCodeInfo;
    mutable bool spjCompileOk = false;

    std::string stdFile;
    CodeFileInfo stdCodeInfo;
    mutable bool stdCompileOk = false;

    mutable int example_index = 0;

    std::vector<std::function<void()>> clean_funs;

    run_code_context_t<T>* belong_context;

    void clear() {
        this->~JudgeType();
    }

    // 需要管理文件等资源,禁止拷贝,现在为了节省时间禁止了移动,后期将会实现
    JudgeType& operator=(const JudgeType&) = delete;
    JudgeType& operator=(JudgeType&&) = delete;
    JudgeType(const JudgeType&) = delete;
    JudgeType(JudgeType&&) = delete;

    JudgeType() = default;

    ~JudgeType() {
        for (auto& fun: clean_funs) {
            fun();
        }
    }

    ProcessStatus init(run_code_context_t<T>& context) {
        belong_context = &context;
        const RuncodeParams& params = context.params;
        // const RuncodeParams& params = context.params;

        // find inFiles, spjFiles
        inFiles = searchFiles(params.dataPath, ".in");
        auto spjFiles = searchFiles(params.spjPath,"spj", startWith);
        auto stdFiles = searchFiles(params.spjPath,"std", startWith);

        if (inFiles.empty()) {
            return ProcessStatus{
                        .result = LESS_STDFILES,
                        .error = "inFiles is empty",
            };
        }

        auto dataPath = params.dataPath;
        auto spjPath = params.spjPath;
        for (auto& file : inFiles) {
            file = osJoin(dataPath, file);
        }
        for (auto& file : spjFiles) {
            file = osJoin(spjPath, file);
        }
        for (auto& file : stdFiles) {
            file = osJoin(spjPath, file);
        }

        userFile = params.filename;
        auto& [userLang, userCodeFile, userBinFile, userCompileStr, userRunStr] = this->userCodeInfo;
        Log::instance().print_kv_range(langMap);
        Log::instance().print_kv_range(suffixMap);
        userLang = const_cast<Language*>(langMap[params.language]);
        Log::instance().getOutput() << "languages: " << params.language << std::endl
                    << "langtype addr: " << userLang << std::endl;
        if (userLang == nullptr) {
            return ProcessStatus{ 
                        .result = PARAMS_ERROR, 
                        .error = "user language is invalid, unknown:" + params.language,
                    };
        }
        if ((filter_lang & userLang->lang_type) == 0) {
            return ProcessStatus{ 
                        .result = PARAMS_ERROR, 
                        .error = "judgeType not support language, unknown:" + params.language,
                    };
        }
        auto user_file_path = std::filesystem::path(userFile);
        if (!std::filesystem::exists(user_file_path)) {
            return ProcessStatus {
                    .result = PARAMS_ERROR,
                    .error = "filename not exists"
            };
        }
        // 若没有userCachePath路径,此时会创建userCachePath
        userCodeFile = userLang->detect(userFile, params.userCachePath);
        userCompileStr = userLang->compileFileStr(userCodeFile);
        userBinFile = userLang->getBinaryFilename(userCodeFile);
        // 最终要进入到userCachePath运行
        userRunStr = userLang->runFileStr(std::filesystem::path(userBinFile).filename().string());

        auto& [spjLang, spjCodeFile, spjBinFile, spjCompileStr, spjRunStr] = this->spjCodeInfo;
        if (!spjFiles.empty()) {
            spjFile = spjFiles.front();
            auto spjLanguage = findSpjFileLangType(spjFile);
            spjLang = std::remove_const_t<Language*>(langMap[spjLanguage]);
            if (spjLang == nullptr) {
                return ProcessStatus{ 
                            .result = PARAMS_ERROR, 
                            .error = "spj language is invalid, unparse filename:" + spjFile,
                        };
            }
            // 若没有spjCachePath路径,此时会创建spjCachePath
            spjCodeFile = spjLang->detect(spjFile, params.spjCachePath);
            spjCompileStr = spjLang->compileFileStr(spjCodeFile);
            spjBinFile = spjLang->getBinaryFilename(spjCodeFile);
            // 最终要进入到spjCachePath运行
            spjRunStr = spjLang->runFileStr(std::filesystem::path(spjBinFile).filename().string());
        }

        auto& [stdLang, stdCodeFile, stdBinFile, stdCompileStr, stdRunStr] = this->stdCodeInfo;
        if (!stdFiles.empty()) {
            stdFile = stdFiles.front();
            auto stdLanguage = findSpjFileLangType(stdFile);
            stdLang = std::remove_const_t<Language*>(langMap[stdLanguage]);
            if (stdLang == nullptr) {
                return ProcessStatus{ 
                            .result = PARAMS_ERROR, 
                            .error = "std language is invalid, unparse filename:" + spjFile,
                        };
            }
            // 若没有spjCachePath路径,此时会创建spjCachePath
            stdCodeFile = stdLang->detect(stdFile, params.spjCachePath);
            stdCompileStr = stdLang->compileFileStr(stdCodeFile);
            stdBinFile = stdLang->getBinaryFilename(stdCodeFile);
            // 最终要进入到spjCachePath运行
            stdRunStr = stdLang->runFileStr(std::filesystem::path(stdBinFile).filename().string());
        }
        Log::instance().getOutput() << "inFiles size:" << inFiles.size() << endl;
        for (const auto& file : inFiles) {
            Log::instance().getOutput() << file << endl;
        }
        Log::instance().getOutput() << "spjFile:" << spjFile << endl
                                    << "spjCodeFile:" << spjCodeFile << endl
                                    << "stdFile:" << stdFile << endl
                                    << "stdCodeFile:" << stdCodeFile << endl
                                    << "userFile:" << userFile << endl
                                    << "userCodeFile:" << userCodeFile << endl;
        return ProcessStatus{ .result = RUN_OK };
    }

    template <class Event>
    run_code_event_space::run_code_event_t next_event(Event) {
        if constexpr (std::is_same_v<Event, run_code_event_space::unknown_event>) {
            return run_code_event_space::end_event{};
        } else {
            return run_code_event_space::unknown_event{};
        }
    }


    // 子类自选实现三个定制函数
    template <class U>
    void pre_execute(U) const {
        Log::instance().getError() << get_class_name<U>() << " pre none" << endl;
    }
    template <class U>
    void post_execute(U, ProcessStatus&) const {
        Log::instance().getError() << get_class_name<U>() << " post none" << endl;
    }
    template <class U>
    decltype(auto) custom_params(U) const {
        return std::tuple<>{};
    }


    // 如果event->execute_impl需要额外传参，则需要实现一个默认版本的
    inline decltype(auto) custom_params(run_code_event_space::spj_verify_event) const {
        return make_tuple(cref(this->inFiles[example_index]));
    }
    inline decltype(auto) custom_params(run_code_event_space::std_code_run_event) const {
        return make_tuple(cref(this->inFiles[example_index]));
    }
    inline decltype(auto) custom_params(run_code_event_space::user_code_run_event) const {
        return make_tuple(cref(this->inFiles[example_index]));
    }
    inline decltype(auto) custom_params(run_code_event_space::twins_spj_std_run_event) const {
        return make_tuple(cref(this->inFiles[example_index]));
    }
    inline decltype(auto) custom_params(run_code_event_space::twins_spj_user_run_event) const {
        return make_tuple(cref(this->inFiles[example_index]));
    }
    inline decltype(auto) custom_params(run_code_event_space::text_compare_event) const {
        return make_tuple(this->get_compare_file());
    }
#ifdef ENABLE_DB_JUDGE
    inline decltype(auto) custom_params(run_code_event_space::database_std_sql_run_event) const {
        return make_tuple(cref(this->inFiles[example_index]));
    }
    inline decltype(auto) custom_params(run_code_event_space::database_user_sql_run_event) const {
        return make_tuple(cref(this->inFiles[example_index]));
    }
    inline decltype(auto) custom_params(run_code_event_space::database_compare_event) const {
        return make_tuple(""s);
    }
    inline void post_execute(run_code_event_space::database_user_sql_run_event, ProcessStatus& status) const {
        status.output = status.extern_info->json();
        belong_context->save_result(status);
    }
    inline void post_execute(run_code_event_space::database_compare_event, ProcessStatus& status) const {
        belong_context->save_result(status);
    }
#endif
    inline void post_execute(run_code_event_space::user_code_run_event, ProcessStatus& status) const {
        belong_context->save_result(status);
    }
    inline void post_execute(run_code_event_space::twins_spj_user_run_event, ProcessStatus& status) const {
        belong_context->save_result(status);
    }
    inline void post_execute(run_code_event_space::text_compare_event, ProcessStatus& status) const {
        belong_context->save_result(status);
    }
    inline void post_execute(run_code_event_space::spj_verify_event, ProcessStatus& status) const {
        belong_context->save_result(status);
    }
    inline void post_execute(run_code_event_space::calculate_result_event, ProcessStatus&) const {
        this->example_index ++;
    }

    // some helper function

    // lazy instantiation
    inline std::string get_compare_file(const string& correct_out_extension = "out", const string& std_out_extension = "stdout") const {
        const string& inFile = inFiles[example_index];
        string stem = inFile.substr(0, inFile.size() - 2);

        if constexpr (std::is_base_of_v<CorrectOutComponent, T>) {
            const auto& outFiles = static_cast<T*>(const_cast<JudgeType*>(this))->outFiles;
            auto checkFile = stem + correct_out_extension;
            Log::instance().getOutput() << "check compare file:" << checkFile << std::endl;
            // if has match outFile, choose default outFile as answer
            if (find(outFiles.begin(), outFiles.end(), checkFile) != outFiles.end()) {
                return checkFile;
            }
        }

        // if has no match outFile, choose stdCode generate stdoutFile as answer
        string stdoutFile = stem + std_out_extension;
        stdoutFile = langMap["default"]->detect(stdoutFile, this->belong_context->params.spjCachePath);
        if (std::filesystem::exists(stdoutFile)) {
            return stdoutFile;
        }
        
        // if has no stdoutFile, return empty string
        return "";
    }
};

template <class T>
const std::vector<seccomp_ruler> JudgeType<T>::disableSysCallSet = base_seccomp_rules;

// incomplete
class TemplateJudge : public JudgeType<TemplateJudge> {
public:
    TemplateJudge();
    template <class Event>
    run_code_event_t next_event(Event e);
};


#endif /* __BASEJUDGE_HPP__ */
